/**   
 * License Agreement for OpenSearchServer Pojodbc
 *
 * Copyright 2008-2013 Emmanuel Keller / Jaeksoft
 * Copyright 2014-2015 OpenSearchServer Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jaeksoft.pojodbc;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashSet;

import com.jaeksoft.pojodbc.connection.ConnectionManager;

/**
 * <p>
 * Represents a new database transaction. Currently, a transaction represents a
 * database connection. Further implementation could share same connection.
 * </p>
 * <p>
 * Transaction automatically closed every Query used.
 * </p>
 * <p>
 * That source code is our recommended way to use it. You have close the
 * transaction in a finally statement to be sure that the database connection
 * will be released.
 * </p>
 * 
 * <pre>
 * Transaction transaction = null;
 * try {
 * 	transaction = connectionManager.getNewTransaction(false, javax.sql.Connection.TRANSACTION_READ_COMMITTED);
 * 	// ... do everything you need ...
 * } finally {
 * 	if (transaction != null)
 * 		transaction.close();
 * }
 * 
 * </pre>
 * 
 */
public class Transaction {

	private Connection cnx;
	private HashSet<Query> queries;

	public Transaction(Connection cnx, boolean autoCommit, Integer transactionIsolation) throws SQLException {
		this.cnx = cnx;
		if (transactionIsolation != null)
			cnx.setTransactionIsolation(transactionIsolation);
		cnx.setAutoCommit(autoCommit);
	}

	void closeQuery(Query query) {
		synchronized (this) {
			query.closeAll();
			queries.remove(query);
		}
	}

	private void closeQueries() {
		synchronized (this) {
			if (queries == null)
				return;
			for (Query query : queries)
				query.closeAll();
			queries.clear();
		}
	}

	/**
	 * Close all queries and the transaction. No commit or rollback are
	 * performed.
	 */
	public void close() {
		synchronized (this) {
			if (cnx == null)
				return;
			synchronized (cnx) {
				closeQueries();
				ConnectionManager.close(null, null, cnx);
				cnx = null;
			}
		}
	}

	/**
	 * Usual JDBC/SQL transaction rollback
	 * 
	 * @throws SQLException
	 *             if any JDBC error occurs
	 */
	public void rollback() throws SQLException {
		synchronized (cnx) {
			cnx.rollback();
		}
	}

	/**
	 * Usual JDBC/SQL transaction commit
	 * 
	 * @throws SQLException
	 *             if any JDBC error occurs
	 */
	public void commit() throws SQLException {
		synchronized (cnx) {
			cnx.commit();
		}
	}

	private void addQuery(Query query) {
		synchronized (this) {
			if (queries == null)
				queries = new HashSet<Query>();
			queries.add(query);
		}
	}

	/**
	 * Create a new Query
	 * 
	 * @param sql
	 *            The native SQL query
	 * @return a new Query instance
	 * @throws SQLException
	 *             if any JDBC error occurs
	 */
	public Query prepare(String sql) throws SQLException {
		Query query = new Query(cnx.prepareStatement(sql));
		addQuery(query);
		return query;
	}

	/**
	 * Create a new Query with autogeneratedkey flag
	 * 
	 * @param sql
	 *            The native SQL query
	 * @return a new Query instance
	 * @throws SQLException
	 *             if any JDBC error occurs
	 */
	public Query prepareWithKeys(String sql) throws SQLException {
		Query query = new Query(cnx.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS));
		addQuery(query);
		return query;
	}

	/**
	 * Create a new Query with standard JDBC properties.
	 * <p>
	 * ResultSetType and ResultSetConcureny are JDBC standard parameters like
	 * ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.TYPE_FORWARD_ONLY,
	 * ResultSet.CONCUR_READ_ONLY, ResultSet.CONCUR_UPDATABLE, ...
	 * </p>
	 * 
	 * @param sql
	 *            The native SQL query
	 * @param resultSetType
	 *            A standard JDBC ResultSet type
	 * @param resultSetConcurency
	 *            A standard JDBC Result concurency property
	 * @return a new Query instance
	 * @throws SQLException
	 *             if any JDBC error occurs
	 */
	public Query prepare(String sql, int resultSetType, int resultSetConcurency) throws SQLException {
		Query query = new Query(cnx.prepareStatement(sql, resultSetType, resultSetConcurency));
		addQuery(query);
		return query;
	}

	/**
	 * A convenient way to directly execute an INSERT/UPDATE/DELETE SQL
	 * statement.
	 * 
	 * @param sql
	 *            The native SQL query
	 * @return the row count
	 * @throws SQLException
	 *             if any JDBC error occurs
	 */
	public int update(String sql) throws SQLException {
		return prepare(sql).update();
	}
}
